<!--
@license
Copyright 2017 GIVe Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<link rel="import" href="../../bower_components/polymer/polymer.html">
<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
<link rel="import" href="../../bower_components/paper-button/paper-button.html">
<link rel="import" href="../../bower_components/paper-slider/paper-slider.html">
<link rel="import" href="../../bower_components/paper-tooltip/paper-tooltip.html">
<link rel="import" href="../../bower_components/paper-toolbar/paper-toolbar.html">
<link rel="import" href="../../bower_components/paper-drawer-panel/paper-drawer-panel.html">
<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout.html">
<link rel="import" href="../genemo-styles.html">
<link rel="import" href="../gene-coor-input/gene-coor-input.html">
<link rel="import" href="../chart-area/chart-area.html">
<link rel="import" href="../chart-track-list/chart-track-group-list.html">
<link rel="import" href="../custom-track-controller/custom-track-controller.html">
<link rel="import" href="../genemo-card/genemo-card.html">
<link rel="import" href="../ref-embedded-behavior/ref-embedded-behavior.html">
<link href="https://fonts.googleapis.com/css?family=Roboto:500,400italic,700italic,700,400" rel="stylesheet" type="text/css">
<!--
### Overview

`<chart-controller>` provides a Web Component element to embed a complete browser
in any html pages.

```html
<chart-controller title-text="My GIVE Browser"
  group-id-list='["genes", "singleCell"]' ref="mm10"></chart-controller>
```

The embedded browser contains three major components:
view area, input for navigation and track controls.

#### View area

View area is the main part of the browser where individual tracks are being shown.
It is implemented by using a `<chart-area>` element.
Multiple views of the same genomic reference are also supported.

Please refer to [`GIVe.ChartArea`](../chart-area/index.html) for further details and [`GIVe.RefEmbeddedBehavior`](../ref-embedded-behavior/index.html) for references used in the display.

#### Input for navigation

`<chart-controller>` provides a input field for navigation purposes, genomic
coordinates in `chrX:XXXXXXX-XXXXXXX` or `chrX XXXXXXX XXXXXXX` formats are
accepted. Also gene names can be typed to search for a specific gene.

The input field is a `<gene-coor-input>` element. See
[`GIVe.GeneCoorInput`](../gene-coor-input/index.html) for
details. Gene annotations are generated from NCBI `gene_info` file.

#### Track controls

Track controls are implemented via a `<chart-track-group-list>` element. See
[`GIVe.ChartTrackGroupList`](../chart-track-list/index.html) for details.

-->
<dom-module id="chart-controller">
  <template>
    <style include="genemo-shared-styles iron-flex iron-flex-alignment iron-positioning">
      :host {
        font-size: 12px;
        @apply(--layout-fit);
      }
      gene-coor-input {
        margin: 0.5em;
      }
      div.smallText {
        font-size: smaller;
        display: block;
        margin-left: 0.5em;
      }
      div.dataReference ::content > * {
        margin: 1em;
      }
      div#navigationHelp {
        margin-right: 2em;
      }
      paper-drawer-panel div.content {
        overflow-y: auto;
      }
      paper-drawer-panel {
        --paper-drawer-panel-drawer-container: {
          padding: 1em;
          background: var(--card-background-color);
        };
      }
      paper-slider {
        width: 100%;
        --paper-slider-input: {
          width: 8em;
        };
      }
      chart-track-group-list {
        --chart-track-list-items-mixin: {
          margin: 0.2em;
        };
      }
    </style>
    <!-- TODO: add paper-dropmenu for ref -->
    <paper-drawer-panel>
      <div drawer class="layout vertical">
        <template is="dom-repeat" items="{{_coorObjs}}">
          <gene-coor-input ref="[[ref]]" label="[[_calcGeneCoorLabel(index)]]"
            floatingLabel="true" value="{{item.coor}}" invalid="[[item.invalid]]">
          </gene-coor-input>
        </template>
        <div class="layout horizontal">
          <paper-button class="colored" raised id="updateChart" on-tap="updateTracks">Update</paper-button>
          <paper-button raised id="cancelUpdateChart" on-tap="resetTracks">Cancel</paper-button>
        </div>
        <genemo-card disable-folding class="flex flexFillGenemoCard">
          <chart-track-group-list ref="[[ref]]" class="genemoBody" id="mainTrackList"
            group-id-list='[[groupIdList]]' setting-key="visibility"
            has-controls='[[hasControls]]' with-filter='[[withFilter]]'
            instant-change='[[instantChange]]'
            default-track-id-list='[[defaultTrackIdList]]'>
          </chart-track-group-list>
        </genemo-card>
        <custom-track-controller ref="[[ref]]" id="mainCTController"></custom-track-controller>
      </div>
      <div main class="layout vertical">
        <paper-toolbar id='mainToolbar'>
          <paper-icon-button icon='menu' paper-drawer-toggle></paper-icon-button>
          <div class="title">[[titleText]]
          </div>
          <div class="tooltipMouseOver" id="navigationHelp">
            <iron-icon icon="icons:help"></iron-icon>
            How to navigate
          </div>
          <paper-tooltip fit-to-visible-bounds for="navigationHelp">
            To navigate GIVe:
            <ul>
              <li><em>Drag horizontally</em> on any tracks or the coordinates to move left / right</li>
              <li>Move mouse cursor <strong>onto the coordinates</strong> and <em>use mouse wheel</em> to zoom in / out.</li>
            </ul>
          </paper-tooltip>
          <content id="additionalCtrls" select=".toolBarCtrls"></content>
        </paper-toolbar>
        <div class="content flex layout vertical">
          <div class="flex relative">
            <chart-area ref="[[ref]]" id="mainChartArea"
              group-id-list='[[groupIdList]]' num-of-subs="{{numOfSubs}}"
              default-track-id-list='[[defaultTrackIdList]]'
              pass-exceptions>
            </chart-area>
          </div>
          <div class="dataReference">
            <content id="reference" select=".chartReference"></content>
          </div>
        </div>
      </div>
    </paper-drawer-panel>
  </template>
  <script>
var GIVe = (function (give) {
  'use strict'

  give.ChartController = Polymer({
    is: 'chart-controller',

    behaviors: [
      give.RefEmbeddedBehavior
    ],

    properties: {
      /**
       * Number of sub views in this controller.
       * Notice that if this setting is different from `coordinates.length`,
       * adjustments will be made to `coordinates` (truncating excessive
       * items or padding with default values).
       * @type {number}
       */
      numOfSubs: {
        type: Number,
        value: 1,
        notify: true,
        observer: '_numOfSubsChanged'
      },

      /**
       * Objects being passed into the text fields
       */
      _coorObjs: {
        type: Array,
        value: function () {
          return []
        }
      },

      /**
       * The track groups included in the embedded browser. Group IDs are
       * specified in the data source. The data source on our server currently
       * provides these track groups:
       * *  `'genes'`: gene annotation tracks, for all available references
       * *  `'encode'`: ENCODE data sets for human and mouse, for `mm9` and `hg19` only
       * *  `'interaction'`: genomic interaction data sets, including those
       * generated from Hi-C (chromatin-chromatin) and MARGI (RNA-chromatin)
       * data, for `mm10`, `hg38` (MARGI) and `hg19` (Hi-C)
       * *  `'singleCell'`: mouse embryo single-cell RNA-seq data set from
       * [Biase *et al.*, *Genome Research*, **24**:1787-1796](http://genome.cshlp.org/content/24/11/1787.full),
       * for `mm10` only
       * @type {Array<string>}
       */
      groupIdList: {
        type: Array,
        value: function () {
          return []
        }
      },

      defaultTrackIdList: {
        type: Array,
        value: function () {
          return []
        }
      },

      /**
       * Coordinates of every view.
       * This is the value that can be specified by HTML attributes.
       * @type {Array<string>}
       */
      coordinates: {
        type: Array,
        value: function () {
          return []
        },
        notify: true
      },

      _threshold: {
        type: Number,
        value: 0.1
      },

      /**
       * The title text that will appear in the embedded browser.
       * @type {string}
       */
      titleText: {
        type: String,
        value: 'GIVe (Genomic Interaction Visualizer)'
      },

      /**
       * Whether the track controller will support filter function
       * @type {boolean}
       */
      withFilter: {
        type: Boolean,
        value: false
      },

      /**
       * Whether the track controller will reflect change immediately
       * without calling this.DOMToTrack().
       * This is always false if hasControls === true
       * @type {boolean}
       */
      instantChange: {
        type: Boolean,
        value: false
      },

      /**
       * Whether the track controller will not show OK/Cancel controls
       * @type {boolean}
       */
      hasControls: {
        type: Boolean,
        value: false
      }
    },

    listeners: {
      'update-coordinate': '_updateCoordinateHandler',
      'update-track-structure': 'updateTrackstructureHandler',
      'show-track-filter': '_showTrackFilterHandler',
      'filter-tracks': '_filterTracksHandler'
    },

    observers: [
      '_coorChanged(coordinates.splices)'
    ],

    ready: function () {
      if (this.coordinates.length > this.numOfSubs) {
        // truncate excessive coordinates
        this.splice('coordinates', this.numOfSubs)
      }
    },

    attached: function () {
      if (this._refObj) {
        this.updateSvg()
      }
      this._isAttached = true
    },

    /**
     * _calcGeneCoorLabel - calculate the label for `<gene-coor-input>`
     *
     * @param  {number} index - The index of view window
     * @returns {string} The returned label
     */
    _calcGeneCoorLabel: function (index) {
      return 'Coordinate or gene name' +
        ((this._coorObjs && this._coorObjs.length > 1)
          ? ' for view #' + (index + 1) : '')
    },

    /**
     * _numOfSubsChanged - sync number of viewWindows to `numOfSubs`
     *
     * @param  {number} newValue new `numOfSubs` value
     * @param  {number} oldValue old `numOfSubs` value
     */
    _numOfSubsChanged: function (newValue, oldValue) {
      if (this._refObj) {
        var defaultWindows = this._refObj.settings.defaultViewWindows
        for (var i = 0; i < newValue; i++) {
          if (!give.ChromRegion.isValidChromRegion(this.coordinates[i])) {
            this.splice('_coorObjs', i, 1, {
              coor: defaultWindows[i % defaultWindows.length],
              invalid: false
            })
          }
        }
      }
    },

    /**
     * Set the reference of embedded browser to `ref`, all the views in the
     * browser will be reverted to reference default values
     *
     * @param {GIVe.RefObject|string} Reference - The GIVe.RefObject or string of the new reference.
     */
    _setRefObj: function (refObj) {
      this._refObj = refObj
      this._numOfSubsChanged(this.numOfSubs)
      if (this._isAttached) {
        this.updateSvg()
      }
      this._threshold = 1
    },

    /**
     * Update the SVG component.
     *
     * Although all chart-controller properties are observed via Polymer and will trigger updates in the views, imperative changes will not be observed. Use this method to update all the views in the browser for those changes manually.
     *
     */
    updateSvg: function () {
      if (this.$.mainChartArea) {
        try {
          var correctedVWindows = this.$.mainChartArea.refreshAll(
            this._coorObjs.map(function (coorObj) {
              return coorObj.coor
            }, this), this._threshold
          )
          this.coordinates = correctedVWindows
        } catch (e) {
          // This will happen if the value is not valid
          // TODO: Show the paper-input elements as invalid
          if (e.data.updatedCoors) {
            // update the coordinates that are changed
            e.data.updatedCoors.forEach(function (coor, index) {
              if (coor) {
                this._coorObjs[index].coor = coor
                this.coordinates[index] = coor
                this.notifyPath(['_coorObjs', index, 'coor'])
              } else {
                this._coorObjs[index].invalid = true
                this.notifyPath(['_coorObjs', index, 'invalid'])
              }
            }, this)
          }
        }
      }
    },

    updateTracks: function () {
      // remove the invalid marks
      this._coorObjs.forEach(function (coorObj, index) {
        coorObj.invalid = false
        // notify Polymer
        this.notifyPath(['_coorObjs', index, 'invalid'])
      }, this)
      if (this.$.mainTrackList) {
        this.$.mainTrackList.DOMToTrack()
        this.$.mainChartArea.updateDOM(true)
        this.updateSvg()
      }
    },

    updateTrackstructure: function () {
    // almost same thing as updateTracks, only needs to update trackSelect
      if (this.$.mainTrackList) {
        this.$.mainTrackList.updateAllGroupDOM()
        this.$.mainChartArea.updateDOM(true)
        this.updateSvg()
      }
    },

    _resetTracks: function () {
      if (this.$.mainTrackList) {
        this.$.mainTrackList.trackToDOM()
        var correctedVWindows = this.$.mainChartArea.getViewWindowStrings()
        this.coordinates = correctedVWindows
        this._threshold = this.$.mainChartArea.threshold
      }
    },

    _updateCoordinateHandler: function (e) {
      if (Array.isArray(e.detail.newWindow)) {
        e.detail.newWindow.forEach(function (newCoor, index) {
          this.splice('coordinates', index, 1, newCoor)
        }, this)
      } else if (e.detail.windowIndex >= 0 &&
        e.detail.windowIndex < this.coordinates.length
      ) {
        this.splice('coordinates', e.detail.windowIndex, 1, e.detail.newWindow)
      }
    },

    updateTrackstructureHandler: function (e) {
      this.updateTrackstructure()
    },

    _coorChanged: function (spliceStatus) {
      if (this._coorObjs) {
        this.splice('_coorObjs', 0)
        this.coordinates.forEach(function (coorText) {
          this.push('_coorObjs', {
            coor: coorText,
            invalid: false
          })
        }, this)
      }
    }

  })

  return give
})(GIVe || {})
  </script>
</dom-module>
